<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
interface AnimateElement extends HTMLElement {
	animateInfo: {
		scale?: number
		x?: number
		y?: number
		z?: number
	}
	prevAnimateInfo: {
		scale?: number
		x?: number
		y?: number
		z?: number
	}
}
const container = ref<HTMLElement>()
let randomAnimate: RandomAnimate
class RandomAnimate {
	container: HTMLElement | null = null
	items: AnimateElement[] = []
	minScale = 0.9
	scaleInterval = 10000 // 缩放间隔
	xInterval = 40000 // 横向位移速度
	yInterval = 40000 // 纵向位移速度
	animateInterval = 16 // 动画间隔 16ms
	index = 1
	animationFrame: number | undefined
	restartTimer: number | undefined
	mutationObserver: MutationObserver | undefined
	//生命周期new对象的时候初始化
	constructor(container: HTMLElement) {
		this.container = container
		this.init()
	}
	init() {
		this.container!.style.position = 'relative'
		//创建dom监视器
		this.createMutation()
	}
	createMutation() {
		this.mutationObserver = new MutationObserver(function (mutationRecords) {
			// mutationRecords变动数组
			//调用初始化方法会手动添加一个child,添加的数组会在回调中包装成MutationRecord 对象
			mutationRecords.forEach((record) => {
				//解构MutationRecord对象
				const { addedNodes, removedNodes } = record
				if (addedNodes.length) {
					//把对象全部添加到类实例中
					randomAnimate.add(
						Array.from(addedNodes).filter((node) => node.nodeType === 1) as AnimateElement[]
					)
				}
				if (removedNodes.length) {
					randomAnimate.remove(
						Array.from(removedNodes).filter((node) => node.nodeType === 1) as AnimateElement[]
					)
				}
			})
		})
		this.mutationObserver.observe(container.value!, {
			childList: true,
			attributes: false
		})
	}
	add(items: AnimateElement | AnimateElement[]) {
		items = Array.isArray(items) ? items : [items]
		items.forEach((item: AnimateElement) => {
			item.style.position = 'absolute'
			item.style.left = '0px'
			item.style.top = '0px'
			item.animateInfo = {}
			item.prevAnimateInfo = {} // 上一状态，用来判断移动方向与缩放状态
			this.setScale(item)
			this.setPosition(item)
			this.setHover(item)
			this.render(item)
		})
		//为每个小模块设置样式
		this.items.push(...items)
	}
	remove(item: AnimateElement | AnimateElement[]) {
		if (Array.isArray(item)) {
			item.forEach((i) => {
				this.remove(i)
			})
		} else {
			// item.parentElement!.removeChild(item)
			const index = this.items.indexOf(item)
			if (index > -1) {
				this.items.splice(index, 1)
			}
		}
	}
	setScale(item: AnimateElement) {
		const maxZ = this.items.length * 100
		const minScale = this.minScale
		const { animateInfo, prevAnimateInfo } = item
		if (!animateInfo.scale) {
			animateInfo.scale = Math.random() * (1 - minScale) + minScale
		} else {
			const scaleInterval = this.scaleInterval
			const scaleStep = ((1 - minScale) / scaleInterval) * this.animateInterval
			const _animate = JSON.parse(JSON.stringify(animateInfo))
			let direction
			if (prevAnimateInfo.scale) {
				direction =
					animateInfo.scale > prevAnimateInfo.scale || animateInfo.scale === minScale ? 1 : -1
			} else {
				direction = Math.random() > 0.5 ? 1 : -1 // 随机开始放大或缩小
			}
			if (direction > 0) {
				// 放大
				animateInfo.scale += scaleStep
				if (animateInfo.scale > 1) {
					animateInfo.scale = 1
				}
			} else {
				// 缩小
				animateInfo.scale -= scaleStep
				if (animateInfo.scale < minScale) {
					animateInfo.scale = minScale
				}
			}
			item.prevAnimateInfo.scale = _animate.scale
		}
		animateInfo.z = ~~(((animateInfo.scale - minScale) / (1 - minScale)) * maxZ)
	}
	setPosition(item: AnimateElement) {
		const maxX = this.container!.clientWidth - item.clientWidth
		const maxY = this.container!.clientHeight - item.clientHeight
		const { animateInfo, prevAnimateInfo } = item
		const _animate = JSON.parse(JSON.stringify(animateInfo))
		if (animateInfo.x === undefined) {
			animateInfo.x = Math.random() * maxX
			animateInfo.y = Math.random() * maxY
		} else {
			let { xInterval, yInterval, animateInterval } = this
			// xInterval = xInterval - Math.random() * 15000
			// yInterval = yInterval - Math.random() * 15000
			const xStep = (maxX / xInterval) * animateInterval
			const yStep = (maxY / yInterval) * animateInterval
			let directionX, directionY
			if (prevAnimateInfo.x === undefined) {
				directionX = Math.random() > 0.5 ? 1 : -1 // 随机开始向左或向右
				directionY = Math.random() > 0.5 ? 1 : -1 // 随机开始向上或向下
			} else {
				directionX = animateInfo.x > prevAnimateInfo.x || animateInfo.x === 0 ? 1 : -1
				directionY = animateInfo.y! > prevAnimateInfo.y! || animateInfo.y === 0 ? 1 : -1
			}
			if (directionX > 0) {
				// 向右
				animateInfo.x += xStep
				if (animateInfo.x > maxX) {
					animateInfo.x = maxX
				}
			} else {
				// 向左
				animateInfo.x -= xStep
				if (animateInfo.x < 0) {
					animateInfo.x = 0
				}
			}
			if (directionY > 0) {
				// 向下
				animateInfo.y! += yStep
				if (animateInfo.y! > maxY) {
					animateInfo.y = maxY
				}
			} else {
				// 向上
				animateInfo.y! -= yStep
				if (animateInfo.y! < 0) {
					animateInfo.y = 0
				}
			}
			item.prevAnimateInfo.x = _animate.x
			item.prevAnimateInfo.y = _animate.y
		}
	}
	setHover(item: AnimateElement) {
		item.addEventListener('mouseenter', () => {
			this.stop()
			clearTimeout(this.restartTimer)
			item.style.zIndex = this.items.length * 10000 + ''
			item.style.transform = item.style.transform.replace(/scale((\d+.?\d*))/, 'scale(1)')
		})
		item.addEventListener('mouseleave', () => {
			item.style.zIndex = item.animateInfo.z + ''
			item.style.transform = item.style.transform.replace(
				/scale((\d+.?\d*))/,
				`scale(${item.animateInfo.scale})`
			)
			this.restartTimer = setTimeout(() => {
				this.start()
			}, 300)
		})
	}
	//解构animateInfo重新渲染
	render(item: AnimateElement) {
		const { x = 10, y = 10, z, scale } = item.animateInfo
		item.style.zIndex = z + ''
		item.style.transform = `translate(${x}px, ${y}px) scale(${scale})`
	}
	stop() {
		this.animationFrame && window.cancelAnimationFrame(this.animationFrame)
	}
	start() {
		this.items.forEach((item: AnimateElement) => {
			this.setScale(item)
			this.setPosition(item)
			this.render(item)
		})
		this.animationFrame = window.requestAnimationFrame(() => {
			this.start()
		})
	}
	destroy() {
		this.stop()
		this.items = []
		this.container = null
		this.mutationObserver && this.mutationObserver.disconnect()
	}
}
function init() {
	randomAnimate = new RandomAnimate(container.value!)
	//添加items children
	const items = Array.from(container.value!.children) as AnimateElement[]
	randomAnimate.add(items)
	randomAnimate.start()
}
onMounted(() => {
	init()
})
onUnmounted(() => {
	randomAnimate.destroy()
})
</script>
<template>
	<div class="word-cloud-container" ref="container">
		<slot></slot>
	</div>
</template>
<style lang="less" scoped>
.word-cloud-container {
	width: 100%;
	height: 100%;
	position: relative;
}
</style>
​
